//-------------------------------------------------------------------
//	Copyright (c) 2012-2014 Zhirnov Andrey
//	This file is part of the "UXGEN-ES" project.
//	See copyright notice in "Engine.h".
//-------------------------------------------------------------------

#pragma once

#include "Graphics/GLshell/RenderState.h"
#include "Graphics/GLshell/VertexInfo.h"

namespace Engine
{
namespace Graphics
{

	struct gl_primitive
	{
		enum type
		{
			POINT			= gles::GL_POINTS,
			LINE			= gles::GL_LINES,
			LINE_LOOP		= gles::GL_LINE_LOOP,
			LINE_STRIP		= gles::GL_LINE_STRIP,
			TRIANGLE		= gles::GL_TRIANGLES,
			TRIANGLE_STRIP	= gles::GL_TRIANGLE_STRIP,
			TRIANGLE_FAN	= gles::GL_TRIANGLE_FAN,
		};
	};
	

	struct gl_index
	{
		enum type
		{
			UBYTE	= gles::GL_UNSIGNED_BYTE,
			UINT8	= UBYTE,
			USHORT	= gles::GL_UNSIGNED_SHORT,
			UINT16	= USHORT,

			// extension 
			UINT	= gles::GL_UNSIGNED_INT,
			UINT32	= UINT
		};
	};


	struct gl_vertex_attrib
	{
		enum type
		{
			NONE	= 0,
			FLOAT	= gles::GL_FLOAT,
			VEC2	= gles::GL_FLOAT_VEC2,
			VEC3	= gles::GL_FLOAT_VEC3,
			VEC4	= gles::GL_FLOAT_VEC4,
			MAT2	= gles::GL_FLOAT_MAT2,
			MAT3	= gles::GL_FLOAT_MAT3,
			MAT4	= gles::GL_FLOAT_MAT4
		};
	};



	struct GLVertexAttribs;


	//
	// Vertex Attrib Info
	//

	struct GLVertexAttribsInfo
	{
	// types
	public:
		struct GLAttribInfo
		{
			gl_vertex_attrib::type	type;
			uint8					index;
			uint8					size;			// size of array
			bool					enabled : 1;
			///
			GLAttribInfo () :
				type(gl_vertex_attrib::NONE), index(-1), size(0), enabled(false)
			{}

			GLAttribInfo (gl_vertex_attrib::type type, uint8 index, uint8 size = 1, bool enabled = true) :
				type(type), index(index), size(size), enabled(enabled)
			{}
			
			bool Compare (const GLAttribInfo &attr) const;
			
			DEBUG_ONLY( string ToString () const; )
		};

		typedef static_array< GLAttribInfo, GLConst::MAX_VERTEX_ATTRIBS >	va_info_t;


	// variables
	private:
		va_info_t	_vaInfo;


	// methods
	public:
		GLVertexAttribsInfo () {}
		
		explicit
		GLVertexAttribsInfo (const GLVertexAttribs &va, uint indexOffset = 0);

		GLVertexAttribsInfo (const GLVertexAttribs &firstVA, uint firstIndexOffset,
							 const GLVertexAttribs &secondVA, uint secondIndexOffset);

		void Set (uint i, const GLAttribInfo &attrib)		{ _vaInfo[i] = attrib; }

		bool Compare (const GLVertexAttribsInfo &) const;

		va_info_t const &	Get () const					{ return _vaInfo; }

		GLAttribInfo const&	operator [] (usize i) const		{ return _vaInfo[i]; }
		//GLAttribInfo	&	operator [] (usize i)			{ return _vaInfo[i]; }

		DEBUG_ONLY( string ToString () const; )
	};



	//
	// Vertex Attribs
	//
	
	struct GLVertexAttribs
	{
	// types
	public:
		typedef GLVertexAttribsInfo::GLAttribInfo	GLAttribInfo;

		struct GLAttrib
		{
			uint32			offset;
			gl_vertex::type	type;
			uint8			index;
			uint8			count;
			bool			norm    : 1;
			bool			enabled : 1;
			///
			GLAttrib () :
				offset(0), index(-1), count(0), type(gl_vertex::FLOAT), norm(false), enabled(false)
			{}

			GLAttrib (uint8 index, uint8 count, gl_vertex::type type, uint32 offset, bool norm) :
				index(index), count(count), type(type), offset(offset), norm(norm), enabled(true)
			{}

			bool Compare (const GLAttrib &attr) const;

			bool Compare (const GLVertexAttribsInfo::GLAttribInfo &info) const;

			GLAttribInfo GetInfo (uint8 indexOffset = 0) const;
		};

		typedef static_array< GLAttrib, GLConst::MAX_VERTEX_ATTRIBS >	vertex_attribs_t;

	private:
		struct AttribSortCmp
		{
			bool operator () (const GLAttrib &left, const GLAttrib &right) const
			{
				return left.index > right.index;
			}
		};


	// variables
	private:
		vertex_attribs_t	_va;
		uint				_count;
		uint16				_stride;
		

	// methods
	public:
		GLVertexAttribs ();

		GLVertexAttribs & Add (const GLAttrib &attrib);

		template <typename C, typename T>
		GLVertexAttribs & Add (T C:: *vertex);

		GLVertexAttribs & SetStride (uint16 stride);
		GLVertexAttribs & Build ();

		vertex_attribs_t const &	Get ()		const	{ return _va; }
		uint						Stride ()	const	{ return _stride; }
		uint						Count ()	const	{ return _count; }
		
		GLAttrib const&	operator [] (usize i)	const	{ return _va[i]; }
		GLAttrib	&	operator [] (usize i)			{ return _va[i]; }

		bool Compare (const GLVertexAttribs &va) const;

		bool IsCompatible (const GLVertexAttribsInfo &attribs) const;
		void GetAttribInfo (GLVertexAttribsInfo &attribs) const;
	};




//----------------------------- GLVertexAttribsInfo -------------------------//
	
	// constructor
	inline GLVertexAttribsInfo::GLVertexAttribsInfo (const GLVertexAttribs &va, uint indexOffset)
	{
		FOR( i, va )
		{
			Set( i, va[i].GetInfo( indexOffset ) );
		}
	}
	
	// constructor
	inline GLVertexAttribsInfo::GLVertexAttribsInfo (const GLVertexAttribs &firstVA, uint firstIndexOffset,
													 const GLVertexAttribs &secondVA, uint secondIndexOffset)
	{
		FOR( i, firstVA )
		{
			Set( i, firstVA[i].GetInfo( firstIndexOffset ) );
		}
		
		FOR( i, secondVA )
		{
			Set( i, secondVA[i].GetInfo( secondIndexOffset ) );
		}
	}
	
	// Compare
	inline bool GLVertexAttribsInfo::Compare (const GLVertexAttribsInfo &other) const
	{
		FOR( i, _vaInfo )
		{
			if ( not _vaInfo[i].Compare( other._vaInfo[i] ) ) {
				return false;
			}
		}
		return true;
	}
	
	// GLVertexAttribsInfo::ToString
	DEBUG_ONLY(
	inline string GLVertexAttribsInfo::ToString () const
	{
		string	s;
		FOR( i, _vaInfo ) {
			s << _vaInfo[i].ToString();
		}
		return s;
	}
	)
	
	// GLAttribInfo::Compare
	inline bool GLVertexAttribsInfo::GLAttribInfo::Compare (const GLAttribInfo &attr) const
	{
		return	( not enabled and not attr.enabled ) or
				( enabled == attr.enabled and
				  type == attr.type and
				  index == attr.index and
				  size == attr.size );
	}
	
	// GLAttribInfo::ToString
	DEBUG_ONLY(
	inline string GLVertexAttribsInfo::GLAttribInfo::ToString () const
	{
		if ( not enabled )
			return string();

		string	s( "{ type: " );
		switch ( type )
		{
			case gl_vertex_attrib::FLOAT :	s << "float";	break;
			case gl_vertex_attrib::VEC2 :	s << "vec2";	break;
			case gl_vertex_attrib::VEC3 :	s << "vec3";	break;
			case gl_vertex_attrib::VEC4 :	s << "vec4";	break;
			case gl_vertex_attrib::MAT2 :	s << "mat2";	break;
			case gl_vertex_attrib::MAT3 :	s << "mat3";	break;
			case gl_vertex_attrib::MAT4 :	s << "mat4";	break;
		}
		s	<< ";  index: " << index << ";  size: " << size << " }\n";
		return s;
	}
	)


//----------------------------- GLVertexAttribs -----------------------------//

	// constructor
	inline GLVertexAttribs::GLVertexAttribs () : _stride(0), _count(0)
	{}
	
	// Add
	inline GLVertexAttribs & GLVertexAttribs::Add (const GLAttrib &attrib)
	{
		_va[_count] = attrib;
		++_count;
		return *this;
	}

	// Add
	template <typename C, typename T>
	inline GLVertexAttribs & GLVertexAttribs::Add (T C:: *vertex)
	{
		_va[_count] = GLAttrib( _count, VertexInfo<T>::Count(), VertexInfo<T>::Type(),
								ReferenceCast< usize >( vertex ), VertexInfo<T>::Normalized() );
		++_count;
		return *this;
	}

	// SetStride
	inline GLVertexAttribs & GLVertexAttribs::SetStride (uint16 stride)
	{
		_stride = stride;
		return *this;
	}
	
	// Build
	inline GLVertexAttribs & GLVertexAttribs::Build ()
	{
		AttribSortCmp cmp;

		Sort( uni_array<GLAttrib>( _va.ptr(), _count ), cmp );
		return *this;
	}

	// Compare
	inline bool GLVertexAttribs::Compare (const GLVertexAttribs &va) const
	{
		FOR( i, _va )
		{
			if ( not _va[i].Compare( va._va[i] ) ) {
				return false;
			}
		}
		return true;
	}
	
	// IsCompatible
	inline bool GLVertexAttribs::IsCompatible (const GLVertexAttribsInfo &attribs) const
	{
		FOR( i, _va )
		{
			if ( not _va[i].Compare( attribs.Get()[i] ) ) {
				return false;
			}
		}
		return true;
	}
	
	// GetAttribInfo
	inline void GLVertexAttribs::GetAttribInfo (GLVertexAttribsInfo &attribs) const
	{
		FOR( i, _va ) {
			attribs.Set( i, _va[i].GetInfo() );
		}
	}


	// GLAttrib::Compare
	inline bool GLVertexAttribs::GLAttrib::Compare (const GLAttrib &attr) const
	{
		return	( not enabled and not attr.enabled ) or
				( enabled == attr.enabled and
				  offset == attr.offset and
				  index == attr.index and
				  count == attr.count and
				  type == attr.type and
				  norm == attr.norm );
	}
	
	// GLAttribInfo::Compare
	inline bool GLVertexAttribs::GLAttrib::Compare (const GLVertexAttribsInfo::GLAttribInfo &info) const
	{
		return GetInfo().Compare( info );
	}
	
	// GLAttribInfo::Compare
	inline GLVertexAttribs::GLAttribInfo GLVertexAttribs::GLAttrib::GetInfo (uint8 indexOffset) const
	{
		if ( not enabled )
			return GLAttribInfo();

		gl_vertex_attrib::type	va_type = gl_vertex_attrib::NONE;

		switch ( count )
		{
			case 1		: va_type = gl_vertex_attrib::FLOAT;	break;
			case 2		: va_type = gl_vertex_attrib::VEC2;		break;
			case 3		: va_type = gl_vertex_attrib::VEC3;		break;
			case 4		: va_type = gl_vertex_attrib::VEC4;		break;
			//case 2*2	: va_type = gl_vertex_attrib::MAT2;		break;
			case 3*3	: va_type = gl_vertex_attrib::MAT3;		break;
			case 4*4	: va_type = gl_vertex_attrib::MAT4;		break;
		}
		ASSERT( va_type != gl_vertex_attrib::NONE );

		return GLAttribInfo( va_type, index + indexOffset, 1, enabled );
	}

}	// Graphics
}	// Engine